package io;

import info.Conference;

import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import org.jdom2.DataConversionException;
import org.jdom2.Element;
import org.jdom2.JDOMException;

import ui.Standards;

/**
 * This class provides access to Conf data for the peer review system.
 * 
 * @author Lun-Wei Chang
 * @author Aaron Chen
 * 
 * @version March 9, 2014
 * 
 */
public class ConferenceAccess extends AbstractAccess {
  
  /** Name of the XML file. */
  private static final String XML_FILE = "Conf.xml";
  
  /** A constant for the Conference ID attribute in the XML file. */
  private static final String ATTRIBUTE_ID = "id";
  
  /** A constant for the Conference identification element in the XML file. */
  private static final String NODE_CONF_ID = "ConfID";
  
  /** A constant for the Conference title element in the XML file. */
  private static final String NODE_TITLE = "ConfTitle";
  
  /** A constant for the Conference location element in the XML file. */
  private static final String NODE_LOCATION = "ConfLocation";
  
  /** A constant for the Conference information element in the XML file. */
  private static final String NODE_INFO = "ConfInfo";
  
  /** A constant for the Conference category element in the XML file. */
  private static final String NODE_CATEGORY = "ConfCat";
  
  /** A constant for the Conference individual categories element in the XML file. */
  private static final String NODE_SUB_CAT = "Cat";
  
  /** A constant for the Conference Program Chair ID element in the XML file. */
  private static final String NODE_PROGRAM_CHAIR = "ConfPC";
  
  /** A constant for the Conference published manuscripts in the XML file. */
  private static final String NODE_MANUSCRIPTS = "ConfManuscripts";
  
  /** A constant for the Manuscript ID in the XML file. */
  private static final String NODE_MANU_ID = "ManuscriptID";
  
  /** A constant for the Conference hosting date element in the XML file. */
  private static final String NODE_DATE = "ConfDate";
  
  /** A constant for the Conference submission deadline element in the XML file. */
  private static final String NODE_SUBMISSION_DEADLINE = "ConfDeadline";
  
  /** A constant for the Conference review deadline element in the XML file. */
  private static final String NODE_REVIEW_DEADLINE = "ConfReviewDeadline";
  
  /** A constant for the Conference location element in the XML file. */
  private static final String NODE_DECISION_DEADLINE = "ConfDecisionDeadline";
  
  /** A constant for the Conference location element in the XML file. */
  private static final String NODE_FINAL_DEADLINE = "ConfFinalDeadline";
  
  /** A constant for the Conference date elements in the XML file. */
  private static final String[] NODE_DATES = {
    NODE_SUBMISSION_DEADLINE,
    NODE_REVIEW_DEADLINE,
    NODE_DECISION_DEADLINE,
    NODE_FINAL_DEADLINE,
    NODE_DATE
  };
  
  
  /** 
   * The constructor to instantiate the ConferenceAccess objects.
   * @throws IOException If there is a problem establishing connectivity to the file.
   * @throws JDOMException If the file cannot be correctly parsed.
   */
  public ConferenceAccess() throws IOException, JDOMException {
    super(XML_FILE);
  } 
  
  /** 
   * Returns a specific conference. 
   * 
   * @param the_id The ID of the conference to be found.
   * @return A conference.
   */
  public Conference getConference(final long the_id) {
    Conference c = null;
    final Element rootnode = getDoc().getRootElement();
    final List<Element> list = rootnode.getChildren(NODE_CONF_ID);
    for (Element e : list) {
      try {
        if (e.getAttribute(ATTRIBUTE_ID).getLongValue() == the_id) {
          c = dataToConference(e);
          break;
        }
      } catch (final DataConversionException err) {
        err.printStackTrace();
      }
    }
    return c; 
  }

  /**
   * Returns the list of all conferences.
   * <p>
   * Warning: this operation may take a long time depending on the number of
   * conferences in the system.
   * @return A List of all conferences.
   */
  public List<Conference> getAllConferences() {
    final Element root_node = getDoc().getRootElement();
    final List<Element> node_list = root_node.getChildren();
    final List<Conference> result_list = new ArrayList<Conference>(node_list.size());
    for (Element node : node_list) {
      result_list.add(dataToConference(node));
    }
    return result_list;
  }

  /**
   * Writes a new conference entry into the database.
   * 
   * @param the_c A new conference.
   * @return true if the operation was a success; false otherwise.
   */
  public boolean createConference(final Conference the_c) {
    boolean result = true;

    final Element rootnode = getDoc().getRootElement();
    final Element e = conferenceToData(the_c, false);
    rootnode.addContent(e);

    // If the write operation has failed, then remove the node so that
    // subsequent write operations do not include this node.
    result = writeToDatabase();
    if (!result) {
      e.detach();
    }

    return result;
  }

  /**
   * Writes over a pre-existing conference in the database.
   * 
   * @param the_paper The Conference data to write into the database.
   * @param the_id The ID of the Conference to overwrite.
   * @return true if the conference to edit was found and overwritten; false
   *         otherwise.
   */
  public boolean editConference(final Conference the_paper, final long the_id) {
    boolean result = false;

    final Element rootnode = getDoc().getRootElement();
    final List<Element> list = rootnode.getChildren();
    Element old_element = null;
    final Element new_element = conferenceToData(the_paper, true);
    for (Element e : list) {
      if (Long.parseLong(e.getAttributeValue(ATTRIBUTE_ID)) == the_id) {
        old_element = e.detach();
        rootnode.addContent(new_element);
        result = true;
        break;
      }
    }

    // If the write operation has failed, then remove the node so that
    // subsequent write operations do not include this node.
    if (result) {
      result = writeToDatabase();
      if (!result) {
        rootnode.addContent(old_element);
        new_element.detach();
      }
    }

    return result;
  }
  
  /**
   * Converts XML element data into a Conference object.
   * @param the_element The element to parse Conference data from.
   * @return A Conference object.
   */
  private Conference dataToConference(final Element the_element) {
    final long id = Long.parseLong(the_element.getAttributeValue(ATTRIBUTE_ID));
    
    final Element categories_element = the_element.getChildren(NODE_CATEGORY).get(0);
    final List<Element> categories_list = categories_element.getChildren(NODE_SUB_CAT);
    final List<String> categories = new ArrayList<String>();
    for (Element e : categories_list) {
      categories.add(e.getText());
    }
    
    String str = the_element.getChildText(NODE_PROGRAM_CHAIR);
    long pc_id = 0;
    if (isNonemptyString(str)) {
      pc_id = Long.parseLong(str);
    }

    final List<Element> manu_nodes = the_element.getChildren(NODE_MANUSCRIPTS);
    final List<Long> manu_ids = new ArrayList<Long>(manu_nodes.size());
    for (Element e : manu_nodes) {
      str = e.getText();
      if (isNonemptyString(str)) {
        manu_ids.add(Long.parseLong(str));
      }
    }
    
    final List<Date> dates = new ArrayList<Date>();
    final SimpleDateFormat df = Standards.DATE_FORMAT;
    try {
      for (int i = 0; i < NODE_DATES.length; i++) {
        str = the_element.getChildText(NODE_DATES[i]);
        if (str != null && !str.isEmpty()) {
          final Date date = df.parse(str);
          dates.add(date);
        }
      }
    } catch (final ParseException e) {
      e.printStackTrace();
    }
    final Conference c = new Conference(id,
                                        the_element.getChildText(NODE_TITLE),
                                        the_element.getChildText(NODE_LOCATION),
                                        the_element.getChildText(NODE_INFO),
                                        categories,
                                        pc_id,
                                        manu_ids,
                                        dates);
    return c;
  }

  /**
   * Translates the Conference object into XML element data.
   * 
   * @param the_c The Conference object to translate.
   * @param the_edit True if this is an edit operation; false if it is a new
   *          entry.
   * @return an XML element node.
   */
  private Element conferenceToData(final Conference the_c, final boolean the_edit) {
    final Element usernode = new Element(NODE_CONF_ID);

    if (!the_edit) {
      final long id = getNextID();
      usernode.setAttribute(ATTRIBUTE_ID, Long.toString(id));
      the_c.setID(id);
    } else {
      usernode.setAttribute(ATTRIBUTE_ID, Long.toString(the_c.getID()));
    }

    usernode.addContent(new Element(NODE_TITLE).setText(the_c.getTitle()));
    usernode.addContent(new Element(NODE_LOCATION).setText(the_c.getHostingVenue()));
    usernode.addContent(new Element(NODE_INFO).setText(the_c.getInfo()));
    
    final Element ec = new Element(NODE_CATEGORY);
    final List<String> categories = the_c.getCategories();
    for (String str : categories) {
      ec.addContent(new Element(NODE_SUB_CAT).setText(str));
    }
    usernode.addContent(ec);
    
    usernode.addContent(new Element(NODE_PROGRAM_CHAIR).
                        setText("" + the_c.getProgramChairID()));

    final Element em = new Element(NODE_MANUSCRIPTS);
    final List<Long> manuscripts = the_c.getPublishedManuscripts();
    for (Long n : manuscripts) {
      usernode.addContent(new Element(NODE_MANU_ID).setText(Long.toString(n)));
    }
    usernode.addContent(em);

    final SimpleDateFormat df = Standards.DATE_FORMAT;
    usernode.addContent(
        new Element(NODE_DATE).setText(df.format(the_c.getHostingDate())));
    String str = df.format(the_c.getSubmissionDeadline());
    usernode.addContent(new Element(NODE_SUBMISSION_DEADLINE).setText(str));
    str = df.format(the_c.getReviewDeadline());
    usernode.addContent(new Element(NODE_REVIEW_DEADLINE).setText(str));
    str = df.format(the_c.getDecisionDeadline());
    usernode.addContent(new Element(NODE_DECISION_DEADLINE).setText(str));
    str = df.format(the_c.getFinalSubmissionDeadline());
    usernode.addContent(new Element(NODE_FINAL_DEADLINE).setText(str));

    return usernode;
  }
  
}
